﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BitTorrentSharp.Client
{
	using Peers;

	/// <summary>
	/// Message parser to parse standard peer-messages.
	/// </summary>
	public class GeneralMessageParser : MessageParser
	{
		/// <summary>
		/// Parse a raw message to a peer message.
		/// </summary>
		/// <param name="rawMessage"></param>
		/// <returns></returns>
		public override Peers.PeerMessage Parse(RawMessage rawMessage)
		{
			// Zero message.
			if (rawMessage.Length == 0)
			{
				return new PeerKeepAliveMessage();
			}

			var type = GetMessageType(rawMessage);

			var content = GetMessageContent(rawMessage);

			switch (type)
			{
				case PeerMessageType.Choke:
					return new ChockMessage();
				case PeerMessageType.Unchoke:
					return new UnchockMessage();
				case PeerMessageType.Interested:
					return new InteresetedMessage();
				case PeerMessageType.NotInterested:
					return new NotInterestedMessage();
				case PeerMessageType.Bitfield:
					var bits = new bool[content.Length * 8];
					Buffer.BlockCopy(content, 0, bits, 0, content.Length);
					return new BitFieldMessage(bits);
				case PeerMessageType.Have:
					{
						var index = ReadIntBigEndian(content, 4);
						return new HaveMessage(index);
					}
				case PeerMessageType.Piece:
					{
						var index = ReadIntBigEndian(content, 4);
						var begin = ReadIntBigEndian(content, 8);
						var length = ReadIntBigEndian(content, 12);

						return new RequestMessage(index, begin, length);
					}
				case PeerMessageType.Cancel:
					{
						var index = ReadIntBigEndian(content, 4);
						var begin = ReadIntBigEndian(content, 8);
						var length = ReadIntBigEndian(content, 12);

						return new CancelMessage(index, begin, length);
					}
				default:
					return null;
			}
		}

		/// <summary>
		/// Get the type information of the message.
		/// </summary>
		/// <param name="message">The raw message item.</param>
		/// <returns>The type of the raw message.</returns>
		private PeerMessageType GetMessageType(RawMessage message)
		{
			var typeValue = ReadIntBigEndian(message.Data, 0);

			return (PeerMessageType)typeValue;
		}

		/// <summary>
		/// Get the content of a raw message.
		/// </summary>
		/// <param name="message">The raw message item.</param>
		/// <returns>The content of the message.</returns>
		private byte[] GetMessageContent(RawMessage message)
		{
			var values = new byte[message.Length - 4];
			Buffer.BlockCopy(message.Data, 4, values, 0, values.Length);

			return values;
		}

		/// <summary>
		/// Read an big-endian <see cref="System.Int32"/> value from a byte sequence.
		/// </summary>
		/// <param name="data">The byte sequence to be read.</param>
		/// <param name="startIndex">The start index of in bytes.</param>
		/// <returns>The integer value.</returns>
		private static int ReadIntBigEndian(byte[] data, int startIndex)
		{
			var length = 4;

			var bytes = new byte[length];
			Buffer.BlockCopy(data, startIndex, bytes, 0, length);
			bytes.EnsureBigEndian();

			return BitConverter.ToInt32(bytes, 0);
		}
	}
}
